namespace SimpleWebServices.Security.Principal
{
    using System;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.Net;
    using System.Security.Principal;
    using System.Web;
    using System.Xml.XPath;
    using Moq;
    using SimpleWebServices;
    using SimpleWebServices.Data;
    using SimpleWebServices.Models;
    using SimpleWebServices.Security.Cryptography;
    using SimpleWebServices.Web;
    using Xunit;

    public sealed class UserFacts
    {
        [Fact]
        public void typedef()
        {
            Assert.True(new ObjectExpectations<User>()
                .DerivesFrom<SimpleWebServices.Data.Record>()
                .IsAbstractBaseClass()
                .XmlRoot("user")
                .Implements<IIdentity>()
                .Implements<IPrincipal>()
                .Result);
        }

        [Fact]
        public void deserialize()
        {
            User actual = (
                "<user>" +
                    "<email address='john.doe@example.com' />" +
                    "<name salutation='Mr' given='John' family='Doe' />" +
                    "<roles>" +
                        "<role>Administrator</role>" +
                        "<role>Public</role>" +
                    "</roles>" +
                "</user>").Deserialize<TestUser>();

            Assert.NotNull(actual);

            Assert.Equal<string>("john.doe@example.com", actual.Email.Address);

            Assert.Equal<string>("Mr", actual.Name.Salutation);
            Assert.Equal<string>("John", actual.Name.Given);
            Assert.Equal<string>("Doe", actual.Name.Family);

            Assert.Equal<int>(2, actual.Roles.Count);
            Assert.Equal<string>("Administrator", actual.Roles[0]);
            Assert.Equal<string>("Public", actual.Roles[1]);
        }

        [Fact]
        public void deserializeEmpty()
        {
            User actual = "<user />".Deserialize<TestUser>();

            Assert.NotNull(actual);
            Assert.NotNull(actual.Name);
            Assert.Empty(actual.Roles);
        }

        [Fact]
        public void serialize()
        {
            User obj = new TestUser
            {
                Email = new Email
                {
                    Address = "john.doe@example.com"
                },
                Name = new PersonalName
                {
                    Salutation = "Mr",
                    Given = "John",
                    Family = "Doe"
                },
                Roles = new Collection<string>
                {
                    "Adminstrator", "Public"
                }
            };

            XPathNavigator navigator = obj.Serialize().CreateNavigator();

            Assert.True((bool)navigator.Evaluate("1=count(/user)"));

            Assert.True((bool)navigator.Evaluate("1=count(/user/email)"));
            Assert.True((bool)navigator.Evaluate("1=count(/user/email[@address='john.doe@example.com'])"));

            Assert.True((bool)navigator.Evaluate("1=count(/user/name)"));
            Assert.True((bool)navigator.Evaluate("1=count(/user/name[@salutation='Mr'][@given='John'][@family='Doe'])"));

            Assert.True((bool)navigator.Evaluate("2=count(/user/roles/role)"));
            Assert.True((bool)navigator.Evaluate("'Adminstrator'=/user/roles/role[1]"));
            Assert.True((bool)navigator.Evaluate("'Public'=/user/roles/role[2]"));
        }

        [Fact]
        public void ctor()
        {
            Assert.NotNull(new TestUser() as User);
        }

        [Fact]
        public void ctor_string_bool()
        {
            User obj = new TestUser("Example", true);

            Assert.Equal<string>("Example", (obj as IIdentity).AuthenticationType);
            Assert.True((obj as IIdentity).IsAuthenticated);
        }

        [Fact]
        public void ctor_stringEmpty_bool()
        {
            User obj = new TestUser(string.Empty, true);

            Assert.Equal<string>(string.Empty, (obj as IIdentity).AuthenticationType);
            Assert.True((obj as IIdentity).IsAuthenticated);
        }

        [Fact]
        public void ctor_stringNull_bool()
        {
            User obj = new TestUser(null as string, true);

            Assert.Null((obj as IIdentity).AuthenticationType);
            Assert.True((obj as IIdentity).IsAuthenticated);
        }

        [Fact]
        public void prop_Email()
        {
            Assert.True(new PropertyExpectations(typeof(TestUser).GetProperty("Email"))
                .XmlElement("email")
                .TypeIs<Email>()
                .DefaultValueIsNotNull()
                .Result);
        }

        [Fact]
        public void prop_Entity()
        {
            Assert.True(new PropertyExpectations(typeof(TestUser).GetProperty("Entity"))
                .XmlIgnore()
                .TypeIs<string>()
                .DefaultValueIsNotNull()
                .Result);
        }

        [Fact]
        public void prop_Entity_get()
        {
            User obj = (
                "<user>" +
                    "<email address='john.doe@example.com' />" +
                    "<name salutation='Mr' given='John' family='Doe' />" +
                    "<roles>" +
                        "<role>Administrator</role>" +
                        "<role>Public</role>" +
                    "</roles>" +
                "</user>").Deserialize<TestUser>();

            string expected = "JLvXktVQ9msf+ABazKU2zw==";
            string actual = MD5Hash.Compute(obj.Entity);

            Assert.Equal<string>(expected, actual);
        }

        [Fact]
        public void prop_Entity_getEmpty()
        {
            string expected = string.Empty;
            string actual = new TestUser().Entity;

            Assert.Equal<string>(expected, actual);
        }

        [Fact]
        public void prop_Name()
        {
            Assert.True(new PropertyExpectations(typeof(TestUser).GetProperty("Name"))
                .XmlElement("name")
                .TypeIs<PersonalName>()
                .DefaultValueIsNotNull()
                .Result);
        }

        [Fact]
        public void prop_Roles()
        {
            Assert.True(new PropertyExpectations(typeof(TestUser).GetProperty("Roles"))
                .XmlArray("roles", "role")
                .TypeIs<Collection<string>>()
                .DefaultValueIsNotNull()
                .Result);
        }

        [Fact]
        public void prop_IIdentity_AuthenticationType()
        {
            Assert.True(new PropertyExpectations(typeof(TestUser).GetInterface("IIdentity").GetProperty("AuthenticationType"))
                .TypeIs<string>()
                .Result);
        }

        [Fact]
        public void prop_IIdentity_IsAuthenticated()
        {
            Assert.True(new PropertyExpectations(typeof(TestUser).GetInterface("IIdentity").GetProperty("IsAuthenticated"))
                .TypeIs<bool>()
                .Result);
        }

        [Fact]
        public void prop_IIdentity_Name()
        {
            Assert.True(new PropertyExpectations(typeof(TestUser).GetInterface("IIdentity").GetProperty("Name"))
                .TypeIs<string>()
                .Result);
        }

        [Fact]
        public void prop_IIdentity_Name_get()
        {
            User obj = new TestUser
            {
                Email = new Email
                {
                    Address = "john.doe@example.com"
                }
            };

            Assert.Equal<string>(obj.Email.Address, (obj as IIdentity).Name);
        }

        [Fact]
        public void prop_IPrincipal_Identity()
        {
            Assert.True(new PropertyExpectations(typeof(TestUser).GetInterface("IPrincipal").GetProperty("Identity"))
                .TypeIs<IIdentity>()
                .Result);
        }

        [Fact]
        public void prop_IPrincipal_Identity_get()
        {
            User obj = new TestUser();

            Assert.Same(obj, (obj as IPrincipal).Identity);
        }

        [Fact]
        public void op_SetFromHttpRequestCookie_whenAlreadyIsNotAuthenticatedUser()
        {
            FakeRepository.Clear();

            try
            {
                DateTimeFactory.Mock = DateTime.UtcNow;

                FakeRepository.Register<AuthenticationRecord<TestUser>>();

                HttpCookie cookie = new HttpCookie("Authentication", AuthenticationRecord.FormatUrn(Token.New()));
                cookie.Expires = DateTimeFactory.UtcNow.AddDays(1);

                var mock = new Mock<HttpContextBase>();
                mock.SetupGet<IPrincipal>(x => x.User).Returns(new AnonymousUser()).Verifiable();
                HttpContextFactory.Mock = mock.Object;
                mock.SetupGet<HttpCookieCollection>(x => x.Request.Cookies).Returns(new HttpCookieCollection { cookie }).Verifiable();
                mock.SetupSet(x => x.User).Callback(x => Assert.IsType<AnonymousUser>(x)).Verifiable();
                HttpContextFactory.Mock = mock.Object;

                User.SetFromHttpRequestCookie<TestUser>();

                mock.VerifyAll();
            }
            finally
            {
                DateTimeFactory.Reset();
                HttpContextFactory.Reset();
                FakeRepository.Reset();
            }
        }

        [Fact]
        public void op_SetFromHttpRequestCookie_whenInRepository()
        {
            FakeRepository.Clear();

            try
            {
                DateTimeFactory.Mock = DateTime.UtcNow;

                FakeRepository.Register<TestUser>();
                FakeRepository.Register<AuthenticationRecord<TestUser>>();

                TestUser user = new TestUser
                {
                    Email = new Email
                    {
                        Address = "john.doe@example.com"
                    },
                    Name = new PersonalName
                    {
                        Salutation = "Mr",
                        Given = "John",
                        Family = "Doe"
                    },
                    Roles = new Collection<string>
                    {
                        "Public"
                    }
                };

                user.Store<TestUser>();

                AuthenticationRecord<TestUser> auth = new AuthenticationRecord<TestUser>
                {
                    User = new Link<TestUser>(user.Urn)
                };

                auth.Store();

                var mock = new Mock<HttpContextBase>();
                mock.SetupGet<IPrincipal>(x => x.User).Returns(null as IPrincipal).Verifiable();
                mock.SetupGet<NameValueCollection>(x => x.Request.ServerVariables).Returns(new NameValueCollection { { "REMOTE_ADDR", "127.0.0.1" } }).Verifiable();
                HttpContextFactory.Mock = mock.Object;
                mock.SetupGet<HttpCookieCollection>(x => x.Request.Cookies).Returns(new HttpCookieCollection { auth.ToHttpCookie(false) }).Verifiable();
                mock.SetupSet(x => x.User).Callback(x => Assert.IsType<TestUser>(x)).Verifiable();
                HttpContextFactory.Mock = mock.Object;

                User.SetFromHttpRequestCookie<TestUser>();

                mock.VerifyAll();
            }
            finally
            {
                DateTimeFactory.Reset();
                HttpContextFactory.Reset();
                FakeRepository.Reset();
            }
        }

        [Fact]
        public void op_SetFromHttpRequestCookie_whenNotInRepository()
        {
            FakeRepository.Clear();

            try
            {
                DateTimeFactory.Mock = DateTime.UtcNow;

                FakeRepository.Register<AuthenticationRecord<TestUser>>();

                HttpCookie cookie = new HttpCookie("Authentication", AuthenticationRecord.FormatUrn(Token.New()));
                cookie.Expires = DateTimeFactory.UtcNow.AddDays(1);

                var mock = new Mock<HttpContextBase>();
                mock.SetupGet<IPrincipal>(x => x.User).Returns(null as IPrincipal).Verifiable();
                HttpContextFactory.Mock = mock.Object;
                mock.SetupGet<HttpCookieCollection>(x => x.Request.Cookies).Returns(new HttpCookieCollection { cookie }).Verifiable();
                mock.SetupSet(x => x.User).Callback(x => Assert.IsType<AnonymousUser>(x)).Verifiable();
                HttpContextFactory.Mock = mock.Object;

                User.SetFromHttpRequestCookie<TestUser>();

                mock.VerifyAll();
            }
            finally
            {
                DateTimeFactory.Reset();
                HttpContextFactory.Reset();
                FakeRepository.Reset();
            }
        }

        [Fact]
        public void op_SetFromHttpRequestCookie_whenNoCookie()
        {
            FakeRepository.Clear();

            try
            {
                DateTimeFactory.Mock = DateTime.UtcNow;

                var mock = new Mock<HttpContextBase>();
                mock.SetupGet<IPrincipal>(x => x.User).Returns(null as IPrincipal).Verifiable();
                HttpContextFactory.Mock = mock.Object;
                mock.SetupGet<HttpCookieCollection>(x => x.Request.Cookies).Returns(new HttpCookieCollection()).Verifiable();
                mock.SetupSet(x => x.User).Callback(x => Assert.IsType<AnonymousUser>(x)).Verifiable();
                HttpContextFactory.Mock = mock.Object;

                User.SetFromHttpRequestCookie<TestUser>();

                mock.VerifyAll();
            }
            finally
            {
                DateTimeFactory.Reset();
                HttpContextFactory.Reset();
                FakeRepository.Reset();
            }
        }

        [Fact]
        public void op_ClearAuthenticationCookie_whenInRepository()
        {
            FakeRepository.Clear();

            try
            {
                DateTimeFactory.Mock = DateTime.UtcNow;

                FakeRepository.Register<TestUser>();
                FakeRepository.Register<AuthenticationRecord<TestUser>>();

                TestUser user = new TestUser
                {
                    Email = new Email
                    {
                        Address = "john.doe@example.com"
                    }
                };

                user.Store<TestUser>();

                AuthenticationRecord<TestUser> auth = new AuthenticationRecord<TestUser>
                {
                    Urn = AuthenticationRecord.FormatUrn(user.Token.Value),
                    User = new Link<TestUser>(user.Urn)
                };

                auth.Store();

                var mock = new Mock<HttpContextBase>();
                mock.SetupGet<HttpCookieCollection>(x => x.Response.Cookies).Returns(new HttpCookieCollection()).Verifiable();
                HttpContextFactory.Mock = mock.Object;

                user.ClearAuthenticationCookie<TestUser>();

                Assert.Equal<DateTime>(DateTimeFactory.UtcNow.AddDays(-1), HttpContextFactory.Current.Response.Cookies["Authentication"].Expires);
                Assert.Equal<string>(string.Empty, HttpContextFactory.Current.Response.Cookies["Authentication"].Value);

                mock.VerifyAll();
            }
            finally
            {
                DateTimeFactory.Reset();
                HttpContextFactory.Reset();
                FakeRepository.Reset();
            }
        }

        [Fact]
        public void op_ClearAuthenticationCookie_whenNotInRepository()
        {
            FakeRepository.Clear();

            try
            {
                DateTimeFactory.Mock = DateTime.UtcNow;

                FakeRepository.Register<TestUser>();
                FakeRepository.Register<AuthenticationRecord<TestUser>>();

                TestUser user = new TestUser
                {
                    Email = new Email
                    {
                        Address = "john.doe@example.com"
                    }
                };

                user.Store<TestUser>();

                var mock = new Mock<HttpContextBase>();
                mock.SetupGet<HttpCookieCollection>(x => x.Response.Cookies).Returns(new HttpCookieCollection()).Verifiable();
                HttpContextFactory.Mock = mock.Object;

                user.ClearAuthenticationCookie<TestUser>();

                Assert.Equal<DateTime>(DateTimeFactory.UtcNow.AddDays(-1), HttpContextFactory.Current.Response.Cookies["Authentication"].Expires);
                Assert.Equal<string>(string.Empty, HttpContextFactory.Current.Response.Cookies["Authentication"].Value);

                mock.VerifyAll();
            }
            finally
            {
                DateTimeFactory.Reset();
                HttpContextFactory.Reset();
                FakeRepository.Reset();
            }
        }

        [Fact]
        public void op_SetAuthenticationCookie_boolFalse_whenInRepository()
        {
            FakeRepository.Clear();

            try
            {
                DateTimeFactory.Mock = DateTime.UtcNow;

                FakeRepository.Register<TestUser>();
                FakeRepository.Register<AuthenticationRecord<TestUser>>();

                TestUser user = new TestUser
                {
                    Email = new Email
                    {
                        Address = "john.doe@example.com"
                    }
                };

                user.Store<TestUser>();

                AuthenticationRecord<TestUser> auth = new AuthenticationRecord<TestUser>
                {
                    Urn = AuthenticationRecord.FormatUrn(user.Token.Value),
                    User = new Link<TestUser>(user.Urn)
                };

                auth.Store();

                var mock = new Mock<HttpContextBase>();
                mock.SetupGet<NameValueCollection>(x => x.Request.ServerVariables).Returns(new NameValueCollection { { "REMOTE_ADDR", "127.0.0.1" } }).Verifiable();
                mock.SetupGet<HttpCookieCollection>(x => x.Response.Cookies).Returns(new HttpCookieCollection()).Verifiable();
                HttpContextFactory.Mock = mock.Object;

                user.SetAuthenticationCookie<TestUser>(false);

                Assert.Equal<DateTime>(DateTimeFactory.UtcNow.AddDays(1), HttpContextFactory.Current.Response.Cookies["Authentication"].Expires);
                Assert.Equal<string>(auth.Urn, HttpContextFactory.Current.Response.Cookies["Authentication"].Value);

                mock.VerifyAll();
            }
            finally
            {
                DateTimeFactory.Reset();
                HttpContextFactory.Reset();
                FakeRepository.Reset();
            }
        }

        [Fact]
        public void op_SetAuthenticationCookie_boolFalse_whenNotInRepository()
        {
            FakeRepository.Clear();

            try
            {
                DateTimeFactory.Mock = DateTime.UtcNow;

                FakeRepository.Register<TestUser>();
                FakeRepository authentications = FakeRepository.Register<AuthenticationRecord<TestUser>>();

                TestUser user = new TestUser
                {
                    Email = new Email
                    {
                        Address = "john.doe@example.com"
                    }
                };

                user.Store<TestUser>();

                var mock = new Mock<HttpContextBase>();
                mock.SetupGet<NameValueCollection>(x => x.Request.ServerVariables).Returns(new NameValueCollection { { "REMOTE_ADDR", "127.0.0.1" } }).Verifiable();
                mock.SetupGet<HttpCookieCollection>(x => x.Response.Cookies).Returns(new HttpCookieCollection()).Verifiable();
                HttpContextFactory.Mock = mock.Object;

                user.SetAuthenticationCookie<TestUser>(false);

                Assert.Equal<DateTime>(DateTimeFactory.UtcNow.AddDays(1), HttpContextFactory.Current.Response.Cookies["Authentication"].Expires);
                Assert.True(HttpContextFactory.Current.Response.Cookies["Authentication"].Value.StartsWith("urn://authentication/"));

                string xpath =
                    "1=count(" +
                        "/repository" +
                        "/object" +
                            "[@token]" +
                            "[@urn]" +
                            "[@created='" + DateTimeFactory.UtcNow.ToXmlString() + "']" +
                            "[@modified='" + DateTimeFactory.UtcNow.ToXmlString() + "']" +
                            "[@status='" + (int)HttpStatusCode.OK + "']" +
                            "[@cacheability='" + (int)HttpCacheability.Server + "']" +
                            "[@etag]" +
                        "/authentication" +
                        "/data" +
                        "/value" +
                            "[@name='ip']" +
                            "[text()='127.0.0.1']" +
                    ")";

                Assert.True((bool)authentications.Xml.CreateNavigator().Evaluate(xpath));
                Assert.True((bool)authentications.Xml.CreateNavigator().Evaluate("1=count(/repository/object/authentication/user[@urn='" + user.Urn + "'])"));

                mock.VerifyAll();
            }
            finally
            {
                DateTimeFactory.Reset();
                HttpContextFactory.Reset();
                FakeRepository.Reset();
            }
        }

        [Fact]
        public void op_Store_whenInRepository()
        {
            FakeRepository.Clear();

            try
            {
                DateTimeFactory.Mock = DateTime.UtcNow;

                User obj = new TestUser
                {
                    Token = Token.New(),
                    Urn = "urn://user/" + Token.New(),
                    Email = new Email
                    {
                        Address = "john.doe@example.com"
                    }
                };

                FakeRepository users = FakeRepository.Register<TestUser>(new
                {
                    token = obj.Token,
                    urn = obj.Urn,
                    created = DateTimeFactory.UtcNow.AddDays(-2),
                    modified = DateTimeFactory.UtcNow.AddDays(-1),
                    status = (int)HttpStatusCode.OK,
                    cacheability = (int)HttpCacheability.Server,
                    etag = MD5Hash.Compute(string.Empty).ToString(),
                    xml = "<user />"
                });

                obj.Store<TestUser>();

                string xpath =
                    "1=count(" +
                        "/repository" +
                        "/object" +
                            "[@token]" +
                            "[@urn]" +
                            "[@created='" + DateTimeFactory.UtcNow.AddDays(-2).ToXmlString() + "']" +
                            "[@modified='" + DateTimeFactory.UtcNow.ToXmlString() + "']" +
                            "[@status='" + (int)HttpStatusCode.OK + "']" +
                            "[@cacheability='" + (int)HttpCacheability.Server + "']" +
                            "[@etag='" + MD5Hash.Compute(obj.Entity) + "']" +
                        "/user" +
                        "/email" +
                            "[@address='" + obj.Email.Address + "']" +
                    ")";

                Assert.True((bool)users.Xml.CreateNavigator().Evaluate(xpath));
            }
            finally
            {
                DateTimeFactory.Reset();
                FakeRepository.Reset();
            }
        }

        [Fact]
        public void op_Store_whenNotInRepository()
        {
            FakeRepository.Clear();

            try
            {
                DateTimeFactory.Mock = DateTime.UtcNow;

                FakeRepository users = FakeRepository.Register<TestUser>();

                User obj = new TestUser
                {
                    Email = new Email
                    {
                        Address = "john.doe@example.com"
                    }
                };

                obj.Store<TestUser>();

                string xpath =
                    "1=count(" +
                        "/repository" +
                        "/object" +
                            "[@token]" +
                            "[@urn]" +
                            "[@created='" + DateTimeFactory.UtcNow.ToXmlString() + "']" +
                            "[@modified='" + DateTimeFactory.UtcNow.ToXmlString() + "']" +
                            "[@status='" + (int)HttpStatusCode.OK + "']" +
                            "[@cacheability='" + (int)HttpCacheability.Server + "']" +
                            "[@etag='" + MD5Hash.Compute(obj.Entity) + "']" +
                        "/user" +
                        "/email" +
                            "[@address='" + obj.Email.Address + "']" +
                    ")";

                Assert.True((bool)users.Xml.CreateNavigator().Evaluate(xpath));
            }
            finally
            {
                DateTimeFactory.Reset();
                FakeRepository.Reset();
            }
        }

        [Fact]
        public void op_IPrincipal_IsInRole_string()
        {
            User obj = new TestUser
            {
                Roles = new Collection<string>
                {
                    "Public"
                }
            };

            Assert.True((obj as IPrincipal).IsInRole("Public"));
        }

        [Fact]
        public void op_IPrincipal_IsInRole_stringNotFound()
        {
            Assert.False((new TestUser() as IPrincipal).IsInRole("Not Found"));
        }

        [Fact]
        public void op_IPrincipal_IsInRole_stringEmpty()
        {
            Assert.False((new TestUser() as IPrincipal).IsInRole(string.Empty));
        }

        [Fact]
        public void op_IPrincipal_IsInRole_stringNull()
        {
            Assert.False((new TestUser() as IPrincipal).IsInRole(null as string));
        }
    }
}